﻿// (c) Copyright Damian Mehers http://damianblog.com/
// This source is subject to the Microsoft Public License.
// See http://www.microsoft.com/resources/sharedsource/licensingbasics/sharedsourcelicenses.mspx.
// All other rights reserved.

using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Text;
using System.Threading;

namespace MceFM.Last.Inject {
  internal class Injecter {
    string bootstrapPath = "";
    public void Start() {
      Trace.TraceInformation("Injecter Starting Injection");
      bootstrapPath = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) + @"\Atadore\MceFM\InjectBootstrap.dll";
      VerifyBootStrapPath();
      Thread thread = new Thread(InjectThread);
      thread.Start();
      thread.Join();
      if(injectException != null) {
        throw injectException;
      }
    }

    private void VerifyBootStrapPath() {
      if(!File.Exists(bootstrapPath)) {
        throw new Exception("Cannot find critical file: " + bootstrapPath);
      }
    }

    Exception injectException;

    private void InjectThread() {
      try {
        Process mediaCenterProcess = Util.FindMediaCenterProcess();
        if(mediaCenterProcess == null) {
          Trace.TraceError("Injecter Can't find Media Center");
          throw new Exception("Injecter Can't find Media Center");
        }
        Trace.TraceInformation("Bootstrap path is '{0}'", bootstrapPath);
        string error;
        bool result =
          DoInject(mediaCenterProcess, bootstrapPath, out error);
        if(!result) {
          Trace.TraceError("Error injecting InjectBootstrap dll: {0}", error);
          throw new Exception("Error injecting bootstrap DLL: " + error);
        } else {
          Trace.TraceInformation("Injected Bootstrap DLL in separate thread");
        }
      } catch(Exception ex) {
        injectException = ex;
      }
    }
  


    private static bool DoInject(
      Process pToBeInjected,
      string sDllPath,
      out string sError) {
      IntPtr hwnd;
      if(!CRT(pToBeInjected, sDllPath, out sError, out hwnd)) //CreateRemoteThread 
      {
        //close the handle, since the method wasn't able to get to that 
        if(hwnd != (IntPtr)0) {
          WINAPI.CloseHandle(hwnd);
        }
        return false;
      }
      // int wee = Marshal.GetLastWin32Error(); 
      return true;
    }


    // From: http://forum.cheatengine.org/viewtopic.php?t=193237
    private static bool CRT(
      Process pToBeInjected,
      string sDllPath,
      out string sError,
      out IntPtr hwnd) {
      sError = String.Empty; //in case we encounter no errors 

      IntPtr hndProc = WINAPI.OpenProcess(
        (0x2 | 0x8 | 0x10 | 0x20 | 0x400),
        //create thread, query info, operation 
        //write, and read 
        1,
        (uint)pToBeInjected.Id);

      hwnd = hndProc;

      if(hndProc == (IntPtr)0) {
        sError = "Unable to attatch to process.\n";
        sError += "Error code: " + Marshal.GetLastWin32Error();
        return false;
      }

      IntPtr lpLLAddress = WINAPI.GetProcAddress(
        WINAPI.GetModuleHandle("kernel32.dll"),
        "LoadLibraryA");

      if(lpLLAddress == (IntPtr)0) {
        sError = "Unable to find address of \"LoadLibraryA\".\n";
        sError += "Error code: " + Marshal.GetLastWin32Error();
        return false;
      }

      IntPtr lpAddress = WINAPI.VirtualAllocEx(
        hndProc,
        (IntPtr)null,
        (IntPtr)sDllPath.Length,
        //520 bytes should be enough 
        (uint)WINAPI.VAE_Enums.AllocationType.MEM_COMMIT |
        (uint)WINAPI.VAE_Enums.AllocationType.MEM_RESERVE,
        (uint)WINAPI.VAE_Enums.ProtectionConstants.PAGE_EXECUTE_READWRITE);

      if(lpAddress == (IntPtr)0) {
        if(lpAddress == (IntPtr)0) {
          sError = "Unable to allocate memory to target process.\n";
          sError += "Error code: " + Marshal.GetLastWin32Error();
          return false;
        }
      }

      byte[] bytes = CalcBytes(sDllPath);
      IntPtr ipTmp;

      WINAPI.WriteProcessMemory(
        hndProc,
        lpAddress,
        bytes,
        (uint)bytes.Length,
        out ipTmp);

      if(Marshal.GetLastWin32Error() != 0) {
        sError = "Unable to write memory to process.";
        sError += "Error code: " + Marshal.GetLastWin32Error();
        return false;
      }

      IntPtr ipThread = WINAPI.CreateRemoteThread(
        hndProc,
        (IntPtr)null,
        (IntPtr)0,
        lpLLAddress,
        lpAddress,
        0,
        (IntPtr)null);

      if(ipThread == (IntPtr)0) {
        sError = "Unable to load dll into memory.";
        sError += "Error code: " + Marshal.GetLastWin32Error();
        return false;
      }

      Trace.TraceInformation("Injecter thinks all is OK");
      return true;
    }

    private static byte[] CalcBytes(string sToConvert) {
      byte[] bRet = Encoding.ASCII.GetBytes(sToConvert);
      return bRet;
    }

    #region Nested type: WINAPI

    private static class WINAPI {
      [DllImport("kernel32.dll", SetLastError = true)]
      public static extern IntPtr OpenProcess(
        UInt32 dwDesiredAccess,
        Int32 bInheritHandle,
        UInt32 dwProcessId);

      [DllImport("kernel32.dll", SetLastError = true)]
      public static extern Int32 CloseHandle(
        IntPtr hObject);

      [DllImport("kernel32.dll", SetLastError = true)]
      public static extern IntPtr GetProcAddress(
        IntPtr hModule,
        string lpProcName);

      [DllImport("kernel32.dll", SetLastError = true)]
      public static extern IntPtr GetModuleHandle(
        string lpModuleName);

      [DllImport("kernel32.dll", SetLastError = true)]
      public static extern IntPtr VirtualAllocEx(
        IntPtr hProcess,
        IntPtr lpAddress,
        IntPtr dwSize,
        uint flAllocationType,
        uint flProtect);

      [DllImport("kernel32.dll", SetLastError = true)]
      public static extern Int32 WriteProcessMemory(
        IntPtr hProcess,
        IntPtr lpBaseAddress,
        byte[] buffer,
        uint size,
        out IntPtr lpNumberOfBytesWritten);

      [DllImport("kernel32.dll", SetLastError = true)]
      public static extern IntPtr CreateRemoteThread(
        IntPtr hProcess,
        IntPtr lpThreadAttribute,
        IntPtr dwStackSize,
        IntPtr lpStartAddress,
        IntPtr lpParameter,
        uint dwCreationFlags,
        IntPtr lpThreadId);

      #region Nested type: VAE_Enums

      public static class VAE_Enums {
#pragma warning disable UnusedMemberInPrivateClass

        #region AllocationType enum

        public enum AllocationType {
          MEM_COMMIT = 0x1000,
          MEM_RESERVE = 0x2000,
          MEM_RESET = 0x80000,
        }

        #endregion

        #region ProtectionConstants enum

        public enum ProtectionConstants {
          PAGE_EXECUTE = 0X10,
          PAGE_EXECUTE_READ = 0X20,
          PAGE_EXECUTE_READWRITE = 0X40,
          PAGE_EXECUTE_WRITECOPY = 0X80,
          PAGE_NOACCESS = 0X01
        }

        #endregion

#pragma warning restore UnusedMemberInPrivateClass
      }

      #endregion
    }

    #endregion
  }
}